﻿using System;
using System.Data.Entity;
using System.Data.Objects.SqlClient;
using System.Linq;
using EntityFramework.Extensions;
using Pyramid.Models;
using Pyramid.Security;

namespace Pyramid.Bll
{
	/// <summary>
	/// 薪资管理帮助类
	/// </summary>
	public class SalaryHelper : BllBase
	{
		public static SalaryHelper NewInstance
		{
			get { return new SalaryHelper(); }
		}

		/// <summary>
		/// 获取员工薪资信息（获取之前先执行 TryCalculateSalary )
		/// </summary>
		public StaffSalaryViewModel GetSalaryList(StaffMonthlyQueryModel model)
		{
			var result = new StaffSalaryViewModel();

			var staffHelper = StaffHelper.NewInstance;
			var db = staffHelper.Db;
			result.Master = db.StaffSalaryMasters.First(x => x.Year == model.Year && x.Month == model.Month);
			result.SalaryParts = db.SalaryParts.ToList();

			var staves = staffHelper.GetStaves(model);
			result.Summaries = from q1 in staves
			                   join q2 in db.StaffSalarySummaries on q1.Id equals q2.StaffId
			                   where q2.MasterId == result.Master.Id
			                   select new StaffSalarySummaryViewModel
			                          {
				                          StaffId = q1.Id,
				                          StaffName = q1.FullName,
				                          DepartmentName = q1.Department.Name,
				                          Gender = q1.Gender,
				                          PositionName = q1.Position.Name,
				                          StaffCode = q1.Code,
				                          TotalSalary = q2.Total
			                          };

			result.Details = from q1 in staves
			                 join q2 in db.StaffSalaryDetails on q1.Id equals q2.StaffId
			                 where q2.MasterId == result.Master.Id
			                 select q2;

			return result;
		}

		/// <summary>
		/// 获取薪资计算主表
		/// </summary>
		public StaffSalaryMaster GetMaster(int year, int month)
		{
			return Db.StaffSalaryMasters.FirstOrDefault(x => x.Year == year && x.Month == month);
		}

		/// <summary>
		/// 标记为已结算
		/// </summary>
		public bool Settle(int masterId)
		{
			var master = Db.StaffSalaryMasters.FirstOrDefault(x => x.Id == masterId);
			if (master == null)
			{
				Alert("数据已丢失，请联系管理员！");
				return false;
			}

			master.Status = (int)FinanceStatus.Settled;
			Db.SaveChanges();
			return true;
		}

		/// <summary>
		/// 计算薪资
		/// </summary>
		public bool CalculateSalary(int year, int month, CustomPrincipal user)
		{
			var db = Db;
			var master = db.StaffSalaryMasters.FirstOrDefault(x => x.Year == year && x.Month == month);
			if (master != null)
			{
				//数据库已存在记录，表明员工薪资已计算，删除数据，重新计算
				var mid = master.Id;
				db.StaffSalaryDetails.Delete(x => x.MasterId == mid);
				db.StaffSalarySummaries.Delete(x => x.MasterId == mid);
				db.StaffSalaryMasters.Remove(master);
				db.SaveChanges();
			}

			#region 1. 计算薪资前对基础数据进行验证

			var attendanceMaster = db.AttendanceMasters.FirstOrDefault(x => x.Year == year && x.Month == month);
			if (attendanceMaster == null)
			{
				Alert(string.Format("{0}年{1}月份还没进行考勤统计，不能计算员工薪资！", year, month));
				return false;
			}
			if (attendanceMaster.Status != (int) DataStatus.Submit)
			{
				Alert(string.Format("{0}年{1}月份的考勤统计还没有提交，不能计算员工薪资！", year, month));
				return false;
			}
			var performanceMaster = db.PerformanceMasters.FirstOrDefault(x => x.Year == year && x.Month == month);
			if (performanceMaster == null)
			{
				Alert(string.Format("{0}年{1}月份还没进行绩效考核统计，不能计算员工薪资！", year, month));
				return false;
			}
			if (performanceMaster.Status != (int) DataStatus.Submit)
			{
				Alert(string.Format("{0}年{1}月份的绩效考核统计还没有提交，不能计算员工薪资！", year, month));
				return false;
			}
			var achievementMaster = db.StaffAchievementGatherMasters.FirstOrDefault(x => x.Year == year && x.Month == month);
			if (achievementMaster == null)
			{
				Alert(string.Format("{0}年{1}月份还没进行业绩汇总统计，不能计算员工薪资！", year, month));
				return false;
			}
			if (achievementMaster.StatusEnum != FinanceStatus.Settled)
			{
				Alert(string.Format("{0}年{1}月份的业绩汇总统计还没有结算，不能计算员工薪资！", year, month));
				return false;
			}

			#endregion

			#region 2. 生成主表

			master = new StaffSalaryMaster
			         {
				         CreateTime = DateTime.Now,
				         Year = year,
				         Month = month,
				         CreateUserId = user.UserId,
				         Status = (int) FinanceStatus.UnSettled
			         };
			db.StaffSalaryMasters.Add(master);
			db.SaveChanges();
			var masterId = master.Id;

			#endregion

			#region 3. 员工薪资配置查询

			var enddate = new DateTime(year, month + 1, 1);
			var setQuery = from item in (from s in db.StaffSalarySetMasters
			                             where SqlFunctions.DateDiff("d", s.EffectiveDate, enddate) > 0
			                             group s by s.StaffId
			                             into g
			                             select g.OrderByDescending(x => x.EffectiveDate).FirstOrDefault())
			               join d in db.StaffSalarySetDetails on item.Id equals d.MasterId
			               select new
			                      {
				                      item.StaffId,
				                      d.PartId,
				                      d.Value,
				                      SettingId = d.Id
			                      };

			#endregion

			var parts = db.SalaryParts.ToList();

			#region 4.1 全勤奖计算

			var part = parts.First(x => x.Code == "sp_full");
			var attendanceMasterId = attendanceMaster.Id;
			var attendancePartId = part.Id;
			var expectedDays = attendanceMaster.ExpectedDays;
			var attendanceSalary = (from q1 in db.Staves
			                        join q2 in setQuery on q1.Id equals q2.StaffId
			                        join q3 in db.AttendanceDetails on q2.StaffId equals q3.StaffId
			                        where q3.MasterId == attendanceMasterId && q2.PartId == attendancePartId
			                        select new StaffSalaryDetailDto
			                               {
				                               MasterId = masterId,
				                               SettingId = q2.SettingId,
				                               PartId = q2.PartId,
				                               Value = q3.ActualDays == expectedDays ? q2.Value : 0,
				                               StaffId = q1.Id
			                               }).ToList();

			#endregion

			#region 4.2 绩效薪资计算

			part = parts.First(x => x.Code == "sp_perf");
			var performancePartId = part.Id;
			var performanceMasterId = performanceMaster.Id;
			var performanceSalary = (from q1 in db.Staves
			                         join q2 in setQuery on q1.Id equals q2.StaffId
			                         join q3 in db.PerfomanceDetails on q2.StaffId equals q3.StaffId
			                         where q3.MasterId == performanceMasterId && q2.PartId == performancePartId
			                         select new StaffSalaryDetailDto
			                                {
				                                MasterId = masterId,
				                                SettingId = q2.SettingId,
				                                PartId = q2.PartId,
				                                Value = q2.Value*q3.Score,
				                                StaffId = q1.Id
			                                }).ToList();

			#endregion

			#region 4.3 业绩薪资计算

			part = parts.First(x => x.Code == "sp_achi");
			var achiMasterId = achievementMaster.Id;
			var achiPartId = part.Id;
			var achiSalary = (from q1 in db.Staves
			                  join q2 in setQuery on q1.Id equals q2.StaffId
			                  join q3 in db.StaffAchievementGatherDetails on q2.StaffId equals q3.StaffId
			                  where q3.MasterId == achiMasterId && q2.PartId == achiPartId
			                  select new StaffSalaryDetailDto
			                         {
				                         MasterId = masterId,
				                         SettingId = q2.SettingId,
				                         PartId = q2.PartId,
				                         Value = q2.Value*q3.TotalAmount,
				                         StaffId = q1.Id
			                         }).ToList();

			#endregion

			#region 4.4 其它部分薪资

			var otherSalary = (from q1 in db.Staves
			                   join q2 in setQuery on q1.Id equals q2.StaffId
			                   join q3 in db.SalaryParts.Where(x => x.Code != "sp_full" && x.Code != "sp_perf" && x.Code != "sp_achi") on q2.PartId equals q3.Id
			                   select new StaffSalaryDetailDto
			                          {
				                          MasterId = masterId,
				                          SettingId = q2.SettingId,
				                          PartId = q2.PartId,
				                          Value = q2.Value*q3.CalculateType,
				                          StaffId = q1.Id
			                          }).ToList();

			#endregion

			var salary = attendanceSalary.Concat(performanceSalary).Concat(achiSalary).Concat(otherSalary).ToList();
			BulkInsert<StaffSalaryDetailDto, StaffSalaryDetail>(salary);

			#region 5. 薪资合计

			var totalSalary = from d in db.StaffSalaryDetails
			                  where d.MasterId == masterId
			                  group d by d.StaffId
			                  into g
			                  select new StaffSalarySummaryDto
			                         {
				                         StaffId = g.Key,
				                         MasterId = masterId,
				                         Total = g.Sum(x => x.Value),
				                         Status = (int) StaffSalaryStatus.Editing
			                         };
			BulkInsert<StaffSalarySummaryDto, StaffSalarySummary>(totalSalary.ToList());

			#endregion

			return true;
		}

		public IQueryable<StaffSalaryDetail> GetMySalaryList(int staffId, int year)
		{
			return Db.StaffSalaryDetails
			         .Include(x => x.Master)
			         .Include(x => x.SettingDetail)
			         .Where(x => x.StaffId == staffId && x.Master.Year == year);
		}
	}
}